#!/home/kamal/work/iamalive/pythonenv/bin/python
# $Id: xtalk.py,v 1.2 2006/10/06 12:30:42 normanr Exp $
import sys,os,xmpp,time,select, logging, threading
sys.path.append('.')
from daemon import Daemon
import psycopg2

LOG_FILENAME='/home/kamal/work/iamalive/bot/python_xmpp_bot/imapresencetracker.log'
APP_NAME='ImaPresenceTrackerDaemon'
CONF_FILENAME='/home/kamal/work/iamalive/bot/python_xmpp_bot/ima_xmpp_bot.conf'
PID_FILENAME='/home/kamal/work/iamalive/bot/python_xmpp_bot/ima_presence_tracker.pid'

# create logger
logger=logging.getLogger(APP_NAME)
logger.setLevel(logging.INFO)

#add a file handler
lfh=logging.FileHandler(LOG_FILENAME)
#lfh=logging.TimedRotatingFileHandler(LOG_FILENAME, 'midnight', 1, backupCount=5)
lfh.setLevel(logging.INFO)
#log.rotator=GZipRotator()

# create a formatter and set the formatter for the handler.
#logFormatter= logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
logFormatter=logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - FILE: %(filename)s - LINE: %(lineno)d - %(message)s')
lfh.setFormatter(logFormatter)

# add the Handler to the logger
logger.addHandler(lfh)

class ImaPresenceTracker(Daemon):
	def __init__(self):
		logger.info('ImaPresenceTracker.__init__ called')
		self.client=None
		self.con=None
		self.auth=None
		self.roster=None
		self.socketlist={}
		self.params={}
		self.db_con=None
		self.db_curs=None
		Daemon.__init__(self, PID_FILENAME)

	def run(self):
		logger.info('ImaPresenceTracker.run called')
		self.read_conf_file()
		self.connect_to_db()
		self.xmpp_sendHeartbeat()
		self.xmpp_disconnectCB()

	def read_conf_file(self):
		logger.warn('ImaPresenceTracker.read_conf_file called')
		self.params={}
		logger.info('conf file is :' + CONF_FILENAME)
		if os.access(CONF_FILENAME, os.R_OK):
			logger.info('conf file is accessible')
			for ln in open(CONF_FILENAME).readlines():
				logger.info('line is : ' + ln)
				if not ln[0] in ('#',';'):
					logger.info('parsing line')
					ln=ln.strip()
					if ( ln.__len__() > 3 ):
						key,val=ln.split('=',1)
						self.params[key.lower()]=val

			logger.info('read and parsed the conf file')

			for mandatory in ['jid','password','subscriber_resource','tracker_resource','db_host','db_port', 'db_name', 'db_user', 'db_password']:
				if mandatory not in self.params.keys():
					open(CONF_FILENAME).write('#Uncomment fields before use and type in correct credentials.\n#JID=romeo@montague.net/resource (/resource is optional)\n#PASSWORD=juliet\nSUBSCRIBER_RESOURCE=subscriber\nTRACKER_RESOURCE=tracker\n#MQ_ID=8888\n#DB_HOST=localhost\n#DB_PORT=5432\n#DB_NAME=<DATABASE NAME>\n#DB_USER=<DB_USER_NAME>\n#DB_PASSWORD=<DB_PASSWORD>\n')
					logger.error('All the required parameters are not present in the conf file')
					logger.error('#Uncomment fields before use and type in correct credentials.\n#JID=romeo@montague.net/resource (/resource is optional)\n#PASSWORD=juliet\nSUBSCRIBER_RESOURCE=subscriber\nTRACKER_RESOURCE=tracker\n#MQ_ID=8888\n#DB_HOST=localhost\n#DB_PORT=5432\n#DB_NAME=<DATABASE NAME>\n#DB_USER=<DB_USER_NAME>\n#DB_PASSWORD=<DB_PASSWORD>\n')
					sys.exit(0)
			logger.info('Read the required parameters from the conf file')
		else:
			logger.info('conf file is NOT accessible')
			logger.error('Conf file ' + CONF_FILENAME + ' doesn\'t exist')
			sys.exit(1)

	def connect_to_db(self):
		logger.warn('ImaPresenceTracker.connect_to_db called')
		try:
			self.db_con=psycopg2.connect(host=self.params['db_host'], port=self.params['db_port'], database=self.params['db_name'], user=self.params['db_user'], password=self.params['db_password'])
			logger.warn('DB connection created')
			self.db_curs=self.db_con.cursor()
		except Exception, e:
			logger.error('Exception received while connecting to the DB server; will terminate')
			logger.error(e.pgerror)
			sys.exit(1)
		logger.info('DB connection successfully established')

	def update_date_of_last_access(self, im_id, to_update_buddy_added_status=False):
		logger.info('ImaPresenceTracker.update_date_of_last_access called')
#		fromJid=xmpp.protocol.JID(im_id)
#		fromJid=fromJid.getNode()
		if im_id==self.params['jid']:
			return

		sql_str=None
		logger.info('Updating last access time of im_id ' + im_id)
		if to_update_buddy_added_status:
			sql_str='update tbl_user SET dola=CURRENT_TIMESTAMP, buddy_added_status=\'C\' where im_id=\'' + im_id + '\''
		else:
			sql_str='update tbl_user SET dola=CURRENT_TIMESTAMP where im_id=\'' + im_id + '\''
		logger.info('sql string is:' + sql_str)
		try:
			self.db_curs.execute(sql_str)
		except Exception, e:
			self.db_con.rollback()
			logger.error('Exception received while updating the last access time of the user')
			logger.error(e.pgerror)
			return
		self.db_con.commit();
		logger.info('Updated last access time of im_id ' + im_id)

	def update_buddy_added_status(self, im_id, added_status):
		logger.info('ImaPresenceTracker.update_buddy_added_status called');
		if im_id==self.params['jid']:
			return
		logger.info('Updating is_buddy status of im_id ' + im_id)
		sql_str='update tbl_user SET buddy_added_status=\'' + added_status + '\' where im_id=\'' + im_id + '\''
		logger.info('sql string is:' + sql_str)
		try:
			self.db_curs.execute(sql_str)
		except Exception, e:
			self.db_con.rollback()
			logger.error('Exception received while updating the buddy_added_status status of the user')
			logger.error(e.pgerror)
			return
		self.db_con.commit();
		logger.info('Updated buddy_added_status status of im_id ' + im_id)

	def get_buddy_added_status(self, im_id):
		logger.info('ImaPresenceTracker.get_buddy_added_status called');
		if im_id==self.params['jid']:
			return False

		buddy_added_status=''
		logger.info('Fetching the buddy_added_status of ' + im_id)
		sql_str='select buddy_added_status from tbl_user where im_id=\'' + im_id + '\''
		logger.info('sql string is:' + sql_str)
		try:
			self.db_curs.execute(sql_str)
			result=self.db_curs.fetchone()
			if result:
				buddy_added_status=result[0]
		except Exception, e:
			logger.error('Exception received while checking the buddy_added_status of im_id ' + im_id)
			logger.error(e.pgerror)

		return buddy_added_status


	def is_registered_im_id(self, im_id):
		logger.info('ImaPresenceTracker.is_registered_im_id called');
		if im_id==self.params['jid']:
			return False

		logger.info('Checking if the im_id ' + im_id + ' is registered or not.')
		sql_str='select count(*) from tbl_user where im_id=\'' + im_id + '\''
		logger.info('sql string is:' + sql_str)
		try:
			self.db_curs.execute(sql_str)
			result=self.db_curs.fetchone()
			if result:
				count=result[0]
				if count >= 1:
					return True
		except Exception, e:
			logger.error('Exception received while checking whether the im_id ' + im_id + ' is registered or not')
			logger.error(e.pgerror)
			return True

		logger.info('The im_id ' + im_id + ' is not registered')
		return False

	def is_activation_code_correct(self, im_id, activation_code):
		logger.info('ImaPresenceTracker.is_activation_code_correct called');
		if im_id==self.params['jid']:
			return False

		logger.info('Checking if the activation code of im_id ' + im_id + ' is correct or not.')
		sql_str='select count(*) from tbl_user where im_id=\'' + im_id + '\' and confirmation_code=\'' + activation_code + '\'';
		logger.info('sql string is:' + sql_str)
		try:
			self.db_curs.execute(sql_str)
			result=self.db_curs.fetchone()
			if result:
				count=result[0]
				if count >= 1:
					logger.info('The activation ocde of im_id ' + im_id + ' is correct')
					return True
		except Exception, e:
			logger.error('Exception received while verifying the activation code of im_id ' + im_id)
			logger.error(e.pgerror)
			return False

		logger.info('The activation ocde of im_id ' + im_id + ' is incorrect')
		return False

	def activate_user(self, im_id):
		logger.info('ImaPresenceTracker.activate_user called');
		if im_id==self.params['jid']:
			return

		logger.info('Updating registration_status of im_id ' + im_id)
		sql_str='update tbl_user SET registration_status=\'C\' where im_id=\'' + im_id + '\''
		logger.info('sql string is:' + sql_str)
		try:
			self.db_curs.execute(sql_str)
		except Exception, e:
			self.db_con.rollback()
			logger.error('Exception received while updating the registration_status of the user')
			logger.error(e.pgerror)
			return
		self.db_con.commit();
		logger.info('Updated registration_status status of im_id ' + im_id)


	def has_user_been_activated(self, im_id):
		logger.info('ImaPresenceTracker.has_user_been_activated called');
		if im_id==self.params['jid']:
			return False

		logger.info('Checking if the user corresponding to im_id ' + im_id + ' is activated or not.')
		sql_str='select registration_status from tbl_user where im_id=\'' + im_id + '\''
		logger.info('sql string is:' + sql_str)
		try:
			logger.info('will execute sql query')
			self.db_curs.execute(sql_str)
			logger.info('Executed sql query; will call fetchone')
			result=self.db_curs.fetchone()
			logger.info('Called fetchone')
			if result:
				status=result[0]
				logger.info('status fetched')
				if status == 'C':
					logger.info('status is C')
					return True
				else:
					logger.info('status is NOT c')
		except Exception, e:
			logger.error('Exception received while checking the registration status of im_id ' + im_id)
			logger.error(e.pgerror)
			return False

		logger.info('The user corresponding to the im_id ' + im_id + ' doesn\'t exist or is not activated yet')
		return False


	def xmpp_connect(self):
		logger.warn('ImaPresenceTracker.xmpp_connect called')
		logger.info('Read the configurations from the conf file');
		logger.info('Jid as read from the conf file is: ' + self.params['jid'])
		jid=xmpp.protocol.JID(self.params['jid'])
		logger.info('Extracted the jid')
		self.client=xmpp.Client(jid.getDomain(),debug=[])
		logger.info('Called xmpp.Client')
		self.con=self.client.connect(None, None, 0)
		logger.info('called connect')

		if not self.con:
			logger.error('Failed to create connection with the server ' + jid.getDomain())
			return False
		
		logger.info('Connection with the server ' + jid.getDomain() + ' established')

		#self.auth=self.client.auth('kamal@iamalive.in', 'namita77')
		self.auth=self.client.auth(jid.getNode(), self.params['password'])
		if not self.auth:
			logger.error('Authentication with the server ' + jid.getDomain() + ' failed')
			return False

		logger.info('Authentication with the server ' + jid.getDomain() + ' successful')
		return True

	def register_handlers(self):
		logger.info('ImaPresenceTracker.register_handlers called')
		self.client.RegisterHandler('message',self.xmpp_messageCB)
		self.client.RegisterHandler('presence',self.xmpp_presenceCB)
		self.client.RegisterDisconnectHandler(self.xmpp_disconnectCB)

	def xmpp_messageCB(self, con, event):
		logger.debug('ImaPresenceTracker.xmpp_messageCB called')
		typ=event.getType()
		fromJid=event.getFrom().getStripped()
		logger.info(fromJid + ' did some chat activity')
		self.update_date_of_last_access(fromJid)

		#if typ in ['message', 'chat', None]:
		if typ in ['chat']:
			mesg=event.getBody()
			if mesg != None:
				logger.info(fromJid + ' says:' + mesg)
				if self.has_user_been_activated(fromJid)==True:
					logger.debug('User is activated')
					m=xmpp.protocol.Message(to=fromJid, body='I am sorry..I am a new born still learning your language. Please talk to me after 3 months...Meanwhile you can interact with the system using IamAliv android app...',typ='chat')
					self.client.send(m)
				else:
					logger.debug('User is not activated')
					mesg=mesg.strip()
					logger.debug('Message is ' + mesg);
					#logger.debug('Will check if activation code is correct or not');
					m=xmpp.protocol.Message(to=fromJid, body='You haven\'t activated your account yet. Activate your account by providing the activation code sent to your email id...',typ='chat')
					self.client.send(m)
					#if self.is_activation_code_correct(fromJid, mesg):
						#self.activate_user(fromJid)
						#m=xmpp.protocol.Message(to=fromJid, body='Your activation code has been verified successfully. You can start using the system using the Android app...',typ='chat')
						#self.client.send(m)
					#else:
						#m=xmpp.protocol.Message(to=fromJid, body='The activation code provided by you is incorrect.',typ='chat')
						#self.client.send(m)
						#m=xmpp.protocol.Message(to=fromJid, body='Please provide the activation code that was sent on your email id',typ='chat')
						#self.client.send(m)

			
	def xmpp_presenceCB(self,sess,presStanza):
		logger.debug('ImaPresenceTracker.xmpp_presenceCB called')
		to=presStanza['to']
		if to:
			toJid=str(presStanza.getTo())

		fro=presStanza['from']
		if fro:
			fromJid=str(presStanza.getFrom().getStripped())
			logger.debug('Presence received from ' + fromJid)

		typ=presStanza.getType()
		if not typ or typ=='available':
			logger.info(fromJid + ' is available')
			self.update_date_of_last_access(fromJid, True)

			show=presStanza.getTag('show')
			if show:
				showData=presStanza.getTagData('show')
				logger.debug('Show tag of ' + fromJid + ' contains ' + showData)

			status=presStanza.getTag('status')
			if status:
				statusData=presStanza.getTagData('status')
				logger.debug('Status tag of ' + fromJid + ' contains ' + statusData)
		elif typ=='unavailable' or typ=='error':
			logger.info(fromJid + ' is unavailable')
		elif typ=='subscribe':
			logger.warn('Friend request received from '+ fromJid)
			if self.is_registered_im_id(fromJid):
				self.roster.Authorize(fromJid)
				logger.warn('Accepted friend request from ' + fromJid)
				buddy_added_status=self.get_buddy_added_status(fromJid)
				if buddy_added_status=='S':
					self.update_buddy_added_status(fromJid, 'C')
				else:
					logger.warn('Also adding the im_id ' + fromJid + ' as friend')
					self.roster.Subscribe(fromJid)
					self.update_buddy_added_status(fromJid, 'R')
					#m=xmpp.protocol.Message(to=fromJid, body='Please provide your activation code that was sent to  your email address if you haven\'t done so already...',typ='chat')
					#self.client.send(m)
		elif typ=='probe':
			logger.warn(fromJid + ' is in probe mode')
		else:
			logger.warn('The presence status of ' + fromJid + ' is unknown')
			return

	def stdio_message(self, message):
		m = xmpp.protocol.Message(to=self.remotejid,body=message,typ='chat')
		self.jabber.send(m)
		pass

	def xmpp_disconnectCB(self):
		logger.warn('ImaPresenceTracker.xmpp_disconnectCB called')

		numOfConnAttempts = 100
		sleepInterval = 10

		if self.params['no_of_conn_attempts']:
			numOfConnAttempts = int(self.params['no_of_conn_attempts'])
		if self.params['sleep_interval']:
			sleepInterval = int(self.params['sleep_interval'])		

		numOfAttemptsMade = 0
		while numOfAttemptsMade <= numOfConnAttempts:
			numOfAttemptsMade += 1
			if not self.xmpp_connect():
				self.con=None
				self.auth=None
				secsToSleep = sleepInterval * numOfAttemptsMade
				logger.error('xmpp_connect() failed in ' + numOfAttemptsMade + ' attempts. Will sleep for ' + secsToSleep + ' secs and retry')
				time.sleep(secsToSleep)
			else:
				break

		if self.con==None or self.auth==None:
			logger.error('Failed to connect to XMPP server after' + numOfAttemptsMade + ' attempts; Will exit now')
			sys.exit(1)

		logger.warn('Connection with the XMPP server established; will register the various handlers now')

		self.register_handlers()
		self.roster=self.client.getRoster()
		logger.info('handlers registered')
		logger.info('Sending intitial presence message to the server');
		self.client.sendInitPresence(requestRoster=0)   # you may need to uncomment this for old server
		logger.info('Initial presence sent')

		#socketlist = {cl.Connection._sock:'xmpp',sys.stdin:'stdio'}
		self.socketlist = {self.client.Connection._sock:'xmpp'}
		online = 1

		while online:
			(i, o, e) = select.select(self.socketlist.keys(),[],[],1)
			for each in i:
				if self.socketlist[each] == 'xmpp':
					self.client.Process(1)
				#elif socketlist[each] == 'stdio':
				#	msg = sys.stdin.readline().rstrip('\r\n')
				#	bot.stdio_message(msg)
				else:
					logger.critical('Exception received during socket communication')
					raise Exception("Unknown socket type: %s" % repr(self.socketlist[each]))

	def xmpp_sendHeartbeat(self):
		logger.debug('ImaPresenceTracker.xmpp_sendHeartbeat called')
		if self.auth:
			logger.debug('sending heartbeat message to the server in the form of a presence message')
			self.client.sendInitPresence(requestRoster=0)
		else:
			logger.error('self.auth is null...wont send intial presence')
		threading.Timer(10, self.xmpp_sendHeartbeat).start()


if __name__ == "__main__":
	logger.info('main invoked')
	daemon = ImaPresenceTracker()
	if len(sys.argv) == 2:
		if 'start' == sys.argv[1]:
			logger.warn('Starting the ImaPresenceTracker daemon process.')
			daemon.start()
		elif 'stop' == sys.argv[1]:
			logger.warn('Stopping the ImaPresenceTracker daemon')
			daemon.stop()
		elif 'restart' == sys.argv[1]:
			logger.warn('Restarting ImaPresenceTracker daemon process')
			daemon.restart()
		else:
			logger.warn('Unknown command; will exit')
			print "Unknown command"
			sys.exit(2)
		sys.exit(0)
	else:
		print "usage: %s start|stop|restart" % sys.argv[0]
		sys.exit(2)

